<?php

define("PANDA_SUCCESS",           0);
define('PANDA_ERR_NULL',          -2);
define("PANDA_ERR_SYSTEM",        -1);
define('PANDA_ERR_ARGS',          -3);

define('PANDA_MEM_SET',           1);
define('PANDA_MEM_GET',           2);
define('PANDA_MEM_INCR',          3);
define('PANDA_MEM_DECR',          4);
define('PANDA_MEM_DELETE',        5);
define('PANDA_DB_SET',            6);
define('PANDA_DB_GET',            7);
define('PANDA_DB_INCR',           8);
define('PANDA_DB_DECR',           9);
define('PANDA_DB_DEL',            10);
define('PANDA_SQL_MPL',           11);
define('PANDA_SQL_PQL',           12);
define('PANDA_SQL_CPL',           13);
define('PANDA_SQL_SQL',           14);

function panda_getErrMsg($ierr)
{
	switch($ierr)
	{
		case PANDA_SUCCESS:       return "Success";
		case PANDA_ERR_NULL:      return "Key is not exist";
		case PANDA_ERR_SYSTEM:    return "System error";
		case PANDA_ERR_ARGS:      return "input args is error";
		default:                  return "Undefined error";
	}
}

class Panda{
	var $sock;
	function __construct($port = 7080 ){
		$this->sock = pfsockopen("localhost", $port, $errno, $errstr);
		if (!$this->sock) {
			error_log("ERROR: $errno - $errstr");
			exit;
		}
		stream_set_timeout($this->sock, 1, 300);
	} 

	function __destruct() {
	} 
/*
向Cache中保存信息。

$hash:  int, ［0, x), 决定信息会存储到那个Cache/Db（Farm）中
$expire: int, [0, x), 过期时间长度
$kv, array, 需要存储的key=>value对
$method, int, 内部参数，为了复用而存在，不建议用户使用(使用默认值)
return: 无
*/
	function set($hash, $expire, $kv, $method = PANDA_MEM_SET){
		$counts = sizeof($kv);
		if($counts == 0)
			return false;
		$input = pack("IIII", $method, $hash, 1*$expire, $counts);
		foreach($kv as $k=>$v)
				$input .=pack("I", strlen($k)).$k.pack("I",strlen($v)).$v;
	        //var_dump($input);	
                fwrite($this->sock, $input);
                return true;
	}
/*
从Cache中删除存储的信息

$hash:  int, ［0, x), 决定信息会存储到那个Cache/Db（Farm）中
$kv, array, 需要存储的key=>value对
$method, int, 内部参数，为了复用而存在，不建议用户使用(使用默认值)

no response for it!
*/

	function delete($hash, $kv, $method = PANDA_MEM_DELETE){
		$counts = sizeof($kv);
//var_dump($kv);
		if($counts == 0)
			return false;
		$input = pack("III", $method, $hash, $counts);
		foreach($kv as $k=>$v)
		{
			$input .=pack("I", strlen($k)).$k;
                }
//                echo "input length = ".strlen($input)."\n";   
//	        var_dump($input);	
                fwrite($this->sock, $input);
                return true;
	}

/*
从Cache中读取信息，如果失败则为false

$hash:  int, ［0, x), 决定信息会存储到那个Cache/Db（Farm）中
$kv, array, 需要读取的key, value将被忽略，通常建议为“”
$method, int, 内部参数，为了复用而存在，不建议用户使用(使用默认值)
return: array, 需要读取的key=>value,如果系统中不存在,return的array中也将不会有此key!失败则返回false
*/

	function get($hash, $kv, $method= PANDA_MEM_GET){
		$arr = array();
		$counts = sizeof($kv);
		if($counts == 0)
			return false;

		$input = pack("III", $method, $hash, $counts);
		foreach($kv as $k=>$v){
			$input .=pack("I", strlen($k)).$k;
		}
		fwrite($this->sock, $input);
		$readlen = $READ = 1024;
		$response = fread($this->sock, $READ);
	//	var_dump($response);
		if(isset($response{7}))
		{
			$sub = substr($response, 0, 8);
			$unpacked = unpack('I2',$sub);         
	//		echo "yes:";  var_dump($unpacked); 
			if(isset($unpacked['1']) && isset($unpacked['2']))
			{
				if($unpacked['2'] != 0)
					return $unpacked['2']; // return errorcode;

				while( $unpacked['1'] > $readlen) 
				{    
				//	echo "not end, continue to read\n"; 
					$response .= fread($this->sock, $READ);
					$readlen += $READ;
				} 
			//	echo "read = "; var_dump($response);

				$idx = 8;
				$sub = substr($response, $idx , 4);
				$unpacked = unpack('I',$sub);      
			//	echo "items = "; var_dump($unpacked);
				$idx += 4;    
				$item = $unpacked['1'];
				for($j = 0; $j<$item ; $j++)
				{
					$str = substr($response, $idx, 4);
					$unpacked = unpack('Iint', $str);
					$idx += 4;
					$key = substr($response, $idx, $unpacked['int']);
			//		echo "key=$key\n";var_dump($unpacked);
					$idx += $unpacked['int'];
					$str = substr($response, $idx, 4);
					$unpacked = unpack('Iint', $str);
					$idx += 4;
					$val = substr($response, $idx, $unpacked['int']);
				//	echo "val =$val"; var_dump($unpacked);
					$idx += $unpacked['int'];
					$arr[$key] = $val;
				}
			}
		}
                else
                   return false; //echo "return error!";
            return $arr;                
	}

/*
key值增量递增

$hash:  int, ［0, x), 决定信息会存储到那个Cache/Db（Farm）中
$key, string, 需要incr的key
$val, unsigned int , 需要incr的大小, 大于零的数字
$method, int, 内部参数，为了复用而存在，不建议用户使用(使用默认值)
return: int, incr后的大小; 失败则返回false
*/

	function incr($hash, $key, $val, $method = PANDA_MEM_INCR){
		$input = pack("II", $method, $hash).pack("I", strlen($key)).$key.pack("l",1*$val);
		fwrite($this->sock, $input);
		$readlen = $READ = 1024;
		$response = fread($this->sock, $READ);
//		var_dump($response);
		if(isset($response{7}))
		{
			$sub = substr($response, 0, 8);
			$unpacked = unpack('I2',$sub);         
		//	echo "yes:";  var_dump($unpacked); 
			if(isset($unpacked['1']) && isset($unpacked['2']))
			{
				if($unpacked['2'] != 0)
                                {
                                 //       return $unpacked['2'];
					return  false;
                                }
				$idx = 8;
				$sub = substr($response, $idx , $unpacked['1'] - $idx);
				$unpacked = unpack('I',$sub);      
			//	echo "items = "; var_dump($unpacked);
				return  $unpacked['1'];
			}
		}
		return false;              
	}
/*
key值增量递减

$hash:  int, ［0, x), 决定信息会存储到那个Cache/Db（Farm）中
$key, string, 需要incr的key
$val, unsigned int , 需要decr的大小, 大于零的数字
$method, int, 内部参数，为了复用而存在，不建议用户使用(使用默认值)
return: int, incr后的大小; 失败则返回false
*/
	function decr($hash, $key, $val, $method = PANDA_MEM_DECR ){
		$input = pack("II", $method, $hash).pack("I", strlen($key)).$key.pack("l",1*$val);
		fwrite($this->sock, $input);

		$readlen = $READ = 1024;
		$response = fread($this->sock, $READ);
		//var_dump($response);
		if(isset($response{7}))
		{
			$sub = substr($response, 0, 8);
			$unpacked = unpack('I2',$sub);         
		//	echo "yes:";  var_dump($unpacked); 
			if(isset($unpacked['1']) && isset($unpacked['2']))
			{
				if($unpacked['2'] != 0)
                                {
                                       //  echo $unpacked['2']."\n";
					return false;
                                }
				$idx = 8;
				$sub = substr($response, $idx , $unpacked['1'] - $idx);
				$unpacked = unpack('I',$sub);      
				//echo "items = "; var_dump($unpacked);
				return  $unpacked['1'];
			}
		}
		return false;              
	}
/*
系统自动把$kv中的信息保存到Cache和DB中

$hash:  int, ［0, x), 决定信息会存储到那个Cache/Db（Farm）中
$expire: int, [0, x), 过期时间长度
$kv, array, 需要存储的key=>value对
return: NULL
*/
	function db_set($hash, $expire, $kv){
             return  $this->set($hash, $expire, $kv, PANDA_DB_SET);
	}
/*
系统自动把$kv中的信息从Cache和DB中删除

$hash:  int, ［0, x), 决定删除那个Cache/Db（Farm）中信息
$kv, array, 需要删除的key=>value对,系统忽略value的值,建议设为""
return: NULL
*/

	function db_delete($hash, $kv){
                return $this->delete($hash, $kv, PANDA_DB_DEL);
	}
/*
从系统中读取$kv中的key对应的信息,如果Cache中没有，系统会主动从DB中读取，并保存到Cache

$hash:  int, ［0, x), 决定信息读取的Cache/Db（Farm）
$kv, array, 需要删除的key=>value对,系统忽略value的值,建议设为""
return: NULL
*/

	function db_get($hash, $kv){
		return $this->get($hash, $kv, PANDA_DB_GET);
	}
/*
系统会在key对应的值基础上增加val,如果Cache没有，系统会自动加载DB中值

$hash:  int, ［0, x), 决定Cache/Db（Farm）
$key, string
$val, unsigned int
return: int, 增量后的结果,如果出错则为false
*/

	function db_incr($hash, $key, $val){
		return $this->incr($hash, $key, $val, PANDA_DB_INCR);
	}
/*
系统会在key对应的值基础上递减val,如果Cache没有，系统会自动加载DB中值

$hash:  int, ［0, x), 决定Cache/Db（Farm）
$key, string
$val, unsigned int
return: int, 递减后的结果,如果出错则为false
*/

	function db_decr($hash, $key, $val ){
		return $this->decr($hash, $key, $val, PANDA_DB_DECR);
	}
/////////////////////////////////////
/*
 执行多条由INSERT/UPDATE/DELETE组成的语句，
 1) 不能是SELECT!只能是INSERT/UPDATE/DELETE
 2) $kv中的key是数字，value是需要执行的处理语句
 系统执行处理语句的顺序有key的大小决定，从小到大执行。

 如果配置文件中使用writedb=0,并设置了viva,则系统不会立即执行SQL,而是写到viva log中,由consumer执行 
 如果配置文件中使用writedb=1,并设置了viva,则系统会立即执行SQL,并写到viva log中
 如果配置文件中使用writedb=1,但没有设置viva,则系统只会执行SQL

 return array, value is retcode, 0 means success: array('0'=>0,'1'=>0) means all success!
 
*/
	function MPL($hash, $kv){
		$counts = sizeof($kv);
		if($counts == 0)
			return false;
                $inmpl="";
                $items = 0; 
		for($i=0; $i< $counts; $i++)
		{ 
			if(isset($kv["$i"]))
			{
				$inmpl .=pack("I", strlen($kv["$i"])).$kv["$i"];
				$items ++;
			}
		}
                if($items < 1)
                   return false;
		$input = pack("III", PANDA_SQL_MPL, $hash, $items).$inmpl;
                fwrite($this->sock, $input);

		$readlen = $READ = 1024;
		$response = fread($this->sock, $READ);
		//var_dump($response);
		if(isset($response{7}))
		{
			$sub = substr($response, 0, 8);
			$unpacked = unpack('I2',$sub);         
		//	echo "yes:";  var_dump($unpacked); 
			if(isset($unpacked['1']) && isset($unpacked['2']))
			{
				if($unpacked['2'] > 0) //args
                                {
                                        $sub = substr($response, 8, $unpacked['1']-8);
                                        $format = "I".$unpacked['2'];
                                        $unpacked = unpack("$format", $sub);
                                        return $unpacked; 
                                }
				return  false; // $unpacked['1'];
			}
		}
		return false;              
	}
/*
 执行多条由INSERT/UPDATE/DELETE组成的语句,最后是一条SELECT语句,系统执行后返回由SELECT获取的数据
 1) 除了最后一条，之前的都不能是SELECT!只能是INSERT/UPDATE/DELETE
 2) $kv中的key是数字，value是需要执行的处理语句,系统执行处理语句的顺序有key的大小决定，从小到大执行。
 3) 最后一条必须是SELECT!

 如果配置文件中使用writedb=0,并设置了viva,则系统不会立即执行SQL,而是写到viva log中,由consumer执行,但系统记录处理语句，不会记录SELECT语句 
 如果配置文件中使用writedb=1,并设置了viva,则系统会立即执行SQL,并写到viva log中,除了SELECT语句
 如果配置文件中使用writedb=1,但没有设置viva,则系统只会执行SQL

*/

	function PQL($hash, $kv){
		$counts = sizeof($kv);
		if($counts == 0)
			return false;
                $inmpl="";
                $items = 0; 
		for($i=0; $i< $counts; $i++)
		{ 
			if(isset($kv["$i"]))
			{
				$inmpl .=pack("I", strlen($kv["$i"])).$kv["$i"];
				$items ++;
			}
		}
                if($items < 1)
                   return false;
		$input = pack("III", PANDA_SQL_PQL, $hash, $items).$inmpl;
                fwrite($this->sock, $input);
                $arr = array();
		$readlen = $READ = 1024;
		$response = fread($this->sock, $READ);
		//var_dump($response);
		if(isset($response{7}))
		{
			$sub = substr($response, 0, 8);
			$unpacked = unpack('I2',$sub);         
			//echo "yes:";  var_dump($unpacked); 
			if(isset($unpacked['1']) && isset($unpacked['2']) && $unpacked['2']==0)
			{
				$sub = substr($response, 8, 8);
				$rf = unpack('I2',$sub);         
                                if(isset($rf['1']) && isset($rf['2']) && $rf['1']>0 && $rf['2']>0 ) 
                                {
                                  // var_dump($rf);  
                                   $used = 16;
                                   $fieldname = array();
                                    for($f = 0; $f < $rf['2']; $f++)
                                   {
                                       $sub = substr($response, $used, 4);
                                       $funp = unpack("I", $sub);
                                       $used += 4;
                                       $fieldname["$f"] = substr($response,$used, $funp['1']);
                                       $used += $funp['1'];
				   }
				   // var_dump($fieldname); 
				    for($row = 0; $row < $rf['1']; $row++)
				    {
					    $subrow = array();
					    for($f = 0; $f < $rf['2']; $f++)
					    {
						    $sub = substr($response, $used, 4);
						    $funp = unpack("I", $sub);
						    $used += 4;
						    $subrow[$fieldname["$f"]] = substr($response,$used, $funp['1']);
						    $used += $funp['1']; 
					    }
					    $arr[$row] = $subrow;
				    }
				}
			}
                        else
                           return false;
		}
		return $arr;              
	}
/*
 参数传递SELECT,UPDATE,INSERT, 系统首先执行SELECT,如果有数据返回，则系统会选择执行UPDATE,如果没有数据返回，则系统会选择执行INSERT

 如果配置文件中使用writedb=0,并设置了viva,则系统不会立即执行SQL,而是写到viva log中,由consumer执行 
 如果配置文件中使用writedb=1,并设置了viva,则系统会立即执行SQL,并写到viva log中
 如果配置文件中使用writedb=1,但没有设置viva,则系统只会执行SQL

*/
	function CPL($hash, $select, $update, $insert){
		$inmpl .=pack("I", strlen($select)).$select;
		$inmpl .=pack("I", strlen($update)).$update;
		$inmpl .=pack("I", strlen($insert)).$insert;
		$input = pack("II", PANDA_SQL_CPL, $hash).$inmpl;
		fwrite($this->sock, $input);

		$readlen = $READ = 1024;
		$response = fread($this->sock, $READ);
		//var_dump($response);
		if(isset($response{7}))
		{
			$sub = substr($response, 0, 8);
			$unpacked = unpack('I2',$sub);         
		//	echo "yes:";  var_dump($unpacked); 
			if(isset($unpacked['1']) && isset($unpacked['2']) && $unpacked['2'] == 0)
				return true;
		}
		return false;              
	}

/*
 参数传递SELECT,系统执行SELECT,返回数据集

*/

	function SQL($hash, $sql){
		if(!isset($sql{10}))			
			return false;
		$input = pack("II", PANDA_SQL_SQL, $hash).pack("I", strlen($sql)).$sql;
		fwrite($this->sock, $input);
                $arr = array();
		$readlen = $READ = 1024;
		$response = fread($this->sock, $READ);
	//	var_dump($response);
		if(isset($response{7}))
		{
			$sub = substr($response, 0, 8);
			$unpacked = unpack('I2',$sub);         
		//	echo "yes:";  var_dump($unpacked); 
			if(isset($unpacked['1']) && isset($unpacked['2']) && $unpacked['2']==0)
			{
				$sub = substr($response, 8, 8);
				$rf = unpack('I2',$sub);         
                                if(isset($rf['1']) && isset($rf['2']) && $rf['1']>0 && $rf['2']>0 ) 
                                {
                                  // var_dump($rf);  
                                   $used = 16;
                                   $fieldname = array();
                                    for($f = 0; $f < $rf['2']; $f++)
                                   {
                                       $sub = substr($response, $used, 4);
                                       $funp = unpack("I", $sub);
                                       $used += 4;
                                       $fieldname["$f"] = substr($response,$used, $funp['1']);
                                       $used += $funp['1'];
				   }
				   // var_dump($fieldname); 
				    for($row = 0; $row < $rf['1']; $row++)
				    {
					    $subrow = array();
					    for($f = 0; $f < $rf['2']; $f++)
					    {
						    $sub = substr($response, $used, 4);
						    $funp = unpack("I", $sub);
						    $used += 4;
						    $subrow[$fieldname["$f"]] = substr($response,$used, $funp['1']);
						    $used += $funp['1']; 
					    }
					    $arr[$row] = $subrow;
				    }
				}
			}
                        else
                           return false;
		}
		return $arr;              
	}

}
//////////////////////////////////////////////////////////////////

function test_set($counts)
{
	$obj = new Panda();
        $arr = array();
        for($i=1; $i<= $counts; $i++)
           $arr["$i"] = "$i";
	$obj->set(0,0, $arr );
}


function test_get($counts)
{
	list($start_usec, $start_sec) = explode(" ", microtime());
	$start_time = ((float) $start_usec + (float) $start_sec);
	for($j=0; $j< 10000; $j++)
	{
		$obj = new Panda();
		$arr = array();
		for($i=1; $i<= $counts; $i++)
			$arr["$i"] = "";
		$ret = $obj->get(0,$arr );
		//var_dump($ret);
	}
	list($end_usec, $end_sec) = explode(" ", microtime());
	$end_time = ((float) $end_usec + (float) $end_sec);

	$total_time = $end_time - $start_time;
	echo "\n".$total_time."\n";
	echo 10000/$total_time;
	// var_dump($ret);
}

function test_pget($counts)
{
	$obj = new Panda();
        $arr = array();
        for($i=1; $i<= $counts; $i++)
           $arr["$i"] = "";

list($start_usec, $start_sec) = explode(" ", microtime());
$start_time = ((float) $start_usec + (float) $start_sec);
for($i=0; $i< 10000; $i++)
{
        $ret = $obj->get(0,$arr );
//var_dump($ret);
}
list($end_usec, $end_sec) = explode(" ", microtime());
$end_time = ((float) $end_usec + (float) $end_sec);

$total_time = $end_time - $start_time;
echo "\n".$total_time."\n";
echo 10000/$total_time;
       // var_dump($ret);
}


function test_incr($key, $val)
{
	$obj = new Panda();
        $arr = array();
        $ret = $obj->incr(0, "$key", $val);
        var_dump($ret);
}

function test_decr($key, $val)
{
	$obj = new Panda();
        $arr = array();
        $ret = $obj->decr(0, "$key", $val);
        var_dump($ret);
}

function test_delete($counts)
{
	$obj = new Panda();
        $arr = array();
        for($i=1; $i<= $counts; $i++)
           $arr["$i"] = "$i";
        $obj->delete(0, $arr);
}


//test_set(3);
//$ret = test_get(3);
/*
test_set(3);
$ret = test_get(3);
test_delete(3);
$ret = test_get(3);
*/
/*
test_set(3);
echo test_incr("3", 10);
echo "\n";
echo test_decr("3", 8);
*/

/*

$obj = new Panda();
$ret = $obj->db_incr(0,"abdecr", 100);
var_dump($ret);
$ret = $obj->db_incr(0,"abdecr", 10);
var_dump($ret);
*/
//$obj->db_delete(0, array("abdecr"=>'',"0"=>'0',"1"=>'1'));


/*
$arr = array();
for($i=1; $i<= 4; $i++)
$arr["$i"] = "$i+$i-$i";
$arr["abc"] = ";delete from table0;";
$obj->db_set(0,0, $arr );


for($i=1; $i<= 4; $i++)
$arr["$i"] = "";
$arr['test'] = "";
$arr['abc'] = '';
$ret = $obj->db_get(0,$arr );
var_dump($ret);
*/

function test_PQL_performance()
{
	list($start_usec, $start_sec) = explode(" ", microtime());
	$start_time = ((float) $start_usec + (float) $start_sec);

	for($j=0; $j< 10000; $j++)
	{
		$obj = new Panda();
		$ret = $obj->PQL(1,array("0"=>"select * from test where keycol='hello1'"));
	}
	list($end_usec, $end_sec) = explode(" ", microtime());
	$end_time = ((float) $end_usec + (float) $end_sec);

	$total_time = $end_time - $start_time;
	echo "\n".$total_time."\n";
	echo 10000/$total_time;
	// var_dump($ret);
}
function test_db_set($counts)
{
	$obj = new Panda();
        $arr = array();
        for($i=1; $i<= $counts; $i++)
           $arr["$i"] = time(NULL)+$i;
	$obj->db_set(0,0, $arr );
}
function test_db_get($counts)
{
	$obj = new Panda();
	$arr = array();
	for($i=1; $i<= $counts; $i++)
		$arr["$i"] = "";
	$ret = $obj->db_get(0, $arr );
	var_dump($ret);
}

function test_MPL()
{
	$obj = new Panda();
	$ret = $obj->MPL(1,array("0"=>"insert into test values('hello1','world',0)", "1"=>"insert into test values('hello2','world',0)"));
	var_dump($ret);
}
function test_PQL()
{
	$obj = new Panda();
	$ret = $obj->PQL(1,array("0"=>"update test set valcol='update value' where keycol='hello1'", "1"=>"select * from test"));
	var_dump($ret);
}


function test_CPL()
{
	$obj = new Panda();
	$ret = $obj->CPL(1,"select * from test where keycol='hello4'","update test set valcol='test' where keycol='hello4'", "insert into test values('hello4','test 123',0)");

	$ret = $obj->SQL(0, "select * from test");
	var_dump($ret);
}

//test_db_set(3);
test_db_get(3);
//test_MPL();
//test_PQL();
//test_CPL();

?>
